/*
 * InodeManager.cpp
 *
 *  Created on: Mar 28, 2010
 *      Author: Lukasz Spintzyk
 */

#include "InodeReader.h"
#include "IOStream.h"
#include "SuperBlock.h"
#include "BlockReader.h"
#include "BlockGroup.h"
#include "Inode.h"
#include "FSException.h"
#include "fs_config.h"
#include "BlocksNumCache.h"


InodeManager::InodeManager(BlockManager& reader, SuperBlock& sb)
	:d_buffer(new byte[8*1024*3])// TODO parametrize the buffer length
	,d_buffer2(new byte[8*1024*3])
	,d_buffer3(new byte[8*1024*3])
	,d_reader(reader)
	,d_sb(sb)
	,d_blkTranslator(d_reader, d_sb)
	,d_blkAdder(d_reader, d_sb)
{
}


/*****************************************************************************/
/*****************************************************************************/

InodeManager::~InodeManager()
{
	delete[] d_buffer;
	delete[] d_buffer2;
	delete[] d_buffer3;
}

/*****************************************************************************/
/*****************************************************************************/

InodePtr InodeManager::getInode(uint32_t nb)
{
	assert(nb < d_sb.d_inodesCount);
	InodeMap_cit findIt = d_inodes.find(nb);
	if (findIt != d_inodes.end())
	{
		return d_inodes[nb];
	}

	uint32_t group_num = ( nb - 1 ) / d_sb.d_inodesPerGroup;
	uint32_t local_num = ( nb - 1 ) % d_sb.d_inodesPerGroup;


	uint32_t block_num = d_sb.getInodeTableBlockOffset() + local_num;

	d_reader.getBlock(group_num, block_num, d_buffer);

    void* buffer = d_buffer + (nb - 1) % d_sb.getInodesPerBlock();
    boost::shared_ptr<Inode> inode = boost::shared_ptr<Inode>(new Inode());
    inode->read(buffer);
    d_inodes[nb]=inode;
    assert(inode->d_num == nb);

    return inode;
}

/*****************************************************************************/
/*****************************************************************************/

void InodeManager::freeInode(uint32_t nb)
{
	assert(nb > 0);
	if (d_inodes.find(nb) != d_inodes.end()){
		d_inodes.erase(nb);
	}

	uint32_t group_num = ( nb - 1 ) / d_sb.d_inodesPerGroup;
	uint32_t local_num = ( nb - 1 ) % d_sb.d_inodesPerGroup;

	BlockDesc& group =	d_reader.getBlockGroupDesc( group_num );

	group.setInode(local_num+1, false);

	d_reader.setBlockGroupDesc( group );

}

/*****************************************************************************/
/*****************************************************************************/

bool InodeManager::setInode(uint32_t inode_num, const InodePtr inode)
{
	uint32_t nb = inode->d_num;
	assert(nb > 0);
	assert(inode->d_num == inode_num);

	d_inodes[nb] = inode;
	uint32_t group_num = ( nb - 1 ) / d_sb.d_inodesPerGroup;
	uint32_t local_num = ( nb - 1 ) % d_sb.d_inodesPerGroup;
	addUsedInodesCount(1);

	inode->d_ctime = time(NULL);

	BlockDesc& group =	d_reader.getBlockGroupDesc( group_num );

	group.setInode(local_num+1, true);

	d_reader.setBlockGroupDesc( group );


	uint32_t block_num = d_sb.getInodeTableBlockOffset() +  local_num;
	d_reader.getBlock(group_num, block_num, d_buffer);

	void* buffer = d_buffer + local_num % d_sb.getInodesPerBlock();
	inode->write(buffer);

	d_reader.setBlock(group_num, block_num, d_buffer);

	return true;
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t InodeManager::getLastUsedBlockNumber(InodePtr inode)
{
	uint32_t last_block_index
		= inode->d_size/d_sb.d_blockSize -1;
	return blkIndex2BlkNum(inode, last_block_index);
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t InodeManager::getLastReservedBlockNumber(InodePtr inode)
{
	//TODO test function
	if (inode->d_blocks == 0)
	{
		throw FSException(EFAULT, "Inode has no reserved blocks");
		// TODO header with errors messages
	}

	uint32_t last_block_index
		= inode->d_blocks -1;
	return blkIndex2BlkNum(inode, last_block_index);
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t InodeManager::blkIndex2BlkNum(InodePtr inode, uint32_t block_index)
{
	return d_blkTranslator.index2Num(inode, block_index);
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t InodeManager::getBlkNumRange(InodePtr inode, uint32_t startIndex, uint32_t* buffer_block)
{
	return d_blkTranslator.getBlkNumRange(inode, startIndex, buffer_block);
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t InodeManager::maxBlocksNumPerInode()
{
	// TODO Test function
	uint32_t size = directBlkNum();
	size += indirectBlkNum();
	size += doubleIndirectBlkNum();
	size += tripleIndirectBlkNum();

	return size;
}

/*****************************************************************************/
/*****************************************************************************/

bool InodeManager::reserveBlocksForInode(InodePtr i, uint32_t num_of_blocks)
{
		if (!hasInodeSpaceForBlocksNum(i, num_of_blocks))
		{
			throw FSException(EFBIG, "No enough free blocks");
		}

		uint32_t last = getLastReservedBlockNumberOrDefault(i);
		std::vector< std::pair<uint32_t, uint32_t> > blocks;
		uint32_t reserved_blocks_num = d_reader.reserveBlocksNearTo(last, num_of_blocks, blocks);

		if (reserved_blocks_num == 0)
		{
			throw FSException(ENOSPC, "No enough space on disk");
			return false;
		}

		for(std::vector< std::pair<uint32_t, uint32_t> >::const_iterator
				cit = blocks.begin();
				cit != blocks.end();
				++cit)
		{
			uint32_t start_block_num = (*cit).first;
			uint32_t blocks_num = (*cit).second;
			d_blkAdder.addBlocksToInode(i, start_block_num, blocks_num);
		}

		// TODO make sure to commit inode
		return true;
}

/*****************************************************************************/
/*****************************************************************************/

bool InodeManager::hasInodeSpaceForBlocksNum(InodePtr inode, uint32_t blocks_num)
{
	uint32_t max = maxBlocksNumPerInode();
	if (blocks_num + inode->d_blocks > max)
	{
		return false;
	}
	return true;
}

/*****************************************************************************/
/*****************************************************************************/


uint32_t InodeManager::getLastReservedBlockNumberOrDefault(InodePtr inode)
{
	try
	{
		return getLastReservedBlockNumber(inode);
	}
	catch(FSException& e)
	{
		return RESERVED_INODES;
	}
}

/*****************************************************************************/
/*****************************************************************************/

void InodeManager::addUsedInodesCount(int32_t add)
{
	d_sb.d_freeInodes -= add;
}

/*****************************************************************************/
/*****************************************************************************/

SuperBlock& InodeManager::getSuperBlock()
{
	return d_sb;
}

/*****************************************************************************/
/*****************************************************************************/

InodePtr InodeManager::createNewInode()
{
	uint32_t node = d_reader.reserveFreeInode();
	InodePtr inode = Inode::newInstance();
	inode->d_num = node;
	inode->d_ctime = time(NULL);

	setInode(node, inode);
	// TODO exception no free inodes mayby add it to reserve FreeInode
	return inode;
}

/*****************************************************************************/
/*****************************************************************************/

void InodeManager::decrementLinksCount(InodePtr inode)
{
	inode->d_linksCount--;

	if (inode->d_linksCount > 0)
	{
		setInode(inode->d_num, inode);
	}
	else
	{
		releaseInodeBlocks(inode);
	}
}

/*****************************************************************************/
/*****************************************************************************/

void InodeManager::releaseInodeBlocks(InodePtr inode)
{
	// TODO free blocks
	BlocksNumCache cache(inode, *this, d_reader);
	uint32_t blocks = inode->d_blocks;
	while(blocks--)
	{
		d_reader.freeBlock(cache.cachedBlkIndex2Num(blocks));
	}

	memset(inode->d_block, 0, sizeof(inode->d_block));
	// TODO write
	freeInode(inode->d_num);


}

/*****************************************************************************/
/*****************************************************************************/

uint32_t  InodeManager::directBlkNum() const
{
	return DIRECT_LAST_INDEX +1;
}


/*****************************************************************************/
/*****************************************************************************/

uint32_t  InodeManager::indirectBlkNum() const
{
	return d_sb.d_blockSize/sizeof(uint32_t);
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t  InodeManager::doubleIndirectBlkNum() const
{
	const uint32_t indirect_blocks = d_sb.d_blockSize/sizeof(uint32_t);
	return indirect_blocks*indirect_blocks;
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t  InodeManager::tripleIndirectBlkNum() const
{
	const uint32_t indirect_blocks = d_sb.d_blockSize/sizeof(uint32_t);
	uint32_t double_indirect = indirect_blocks*indirect_blocks;
	return double_indirect*indirect_blocks;
}

